﻿/*  Copyright (C) 2013 AmaroK86 (marcidm 'at' hotmail 'dot' com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.IO;
using System.Xml;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;
using AmaroK86.Tools;
using AmaroK86.MassEffect3; // MEPath + TOCHandler
using AmaroK86.MassEffect3.PCC;
using AmaroK86.MassEffect3.Image;
using AmaroK86.MassEffect3.ZlibBlock;
using AmaroK86.MassEffect3.UnrealObjects;
using Gibbed.IO;
using ZLibNet;
using System.Text.RegularExpressions;

namespace AmaroK86.MassEffect3.Texplorer
{
    public partial class mainForm : Form
    {
        Texture2D currentTex;
        DBHandler dbHandler = new DBHandler();

        INIHandler iniHnd = new INIHandler();
        TOCHandler tocHndl = new TOCHandler(ME3Paths.tocFile, ME3Paths.gamePath);

        string defaultPccTabName = "Pcc list"; 
        int _countTextures;
        public int countTextures
        {
            set
            {
                _countTextures = value;
                toolStripStatusLabel.Text = "Loaded " + _countTextures + " texture" + ((_countTextures == 1) ? "" : "s");
            }
        }

        public mainForm()
        {
            InitializeComponent();

            countTextures = dbHandler.completeTexList(trvAll);
            dbHandler.completePccList(trvPcc);
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            dbHandler.Close();

            new WindowProgressForm(tocHndl.fixAll, null).ShowDialog();
            tocHndl.saveToFile();
        }

        private void trvAll_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (trvAll.SelectedNode == null)
                return;
            TreeNode nodeSel = trvAll.SelectedNode;
            openTextureData(nodeSel);
        }

        private void openTextureData(TreeNode nodeSel)
        {
            if (nodeSel.Nodes.Count > 0)
                return;

            /*System.Reflection.Assembly thisExe;
            thisExe = System.Reflection.Assembly.GetExecutingAssembly();
            string[] resources = thisExe.GetManifestResourceNames();
            string list = "";

            // Build the string of resources.
            foreach (string resource in resources)
                list += resource + "\r\n";

            MessageBox.Show(list);
            System.Reflection.Assembly thisExe;
            thisExe = System.Reflection.Assembly.GetExecutingAssembly();
            System.IO.Stream file =
                thisExe.GetManifestResourceStream("AmaroK86.MassEffect3.Texplorer.Resources.loading.gif");
            this.pictureBox.Invoke(new MethodInvoker(
            delegate()
            {
                pictureBox.Image = System.Drawing.Image.FromStream(file);
                pictureBox.Refresh();
            }));*/
            

            try
            {
                string pccFile = nodeSel.Name;
                string texName = nodeSel.Text;

                PCCObject pccObj = new PCCObject(ME3Paths.cookedPath + pccFile);
                currentTex = new Texture2D(pccObj.Exports[texName, "Texture2D"]);

                string imgFormat = (string)currentTex.properties["Format"].Value;

                gbxTexture.Text = currentTex.exportRef.PackageFullName + "." + texName;
                tbxTexFormat.Text = imgFormat.Substring(3);
                tbxTexSize.Text = currentTex.imgList.maxImgSize.ToString();

                Bitmap previewBitmap = null;
                ImageSize previewSize = (currentTex.imgList.Count == 1) ? currentTex.imgList[0].imgSize : currentTex.imgList.Where(image => image.storageType != ImageInfo.storage.empty && (image.imgSize.width <= 256 && image.imgSize.height <= 256)).First().imgSize;

                string previewFileName = ImageHandler.showPreview(currentTex, previewSize);

                if (previewBitmap != null)
                    previewBitmap.Dispose();

                // Stretches the image to fit the pictureBox.
                if (File.Exists(previewFileName))
                {
                    if (pictureBox.Image != null)
                        pictureBox.Image.Dispose();
                    pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
                    int stretchWidth = (previewSize.width / 256 < 1) ? 1 : (int)previewSize.width / 256;
                    int stretchHeight = (previewSize.height / 256 < 1) ? 1 : (int)previewSize.height / 256;
                    int previewSizeWidth = (int)previewSize.width / Math.Max(stretchWidth, stretchHeight);
                    int previewSizeHeight = (int)previewSize.height / Math.Max(stretchWidth, stretchHeight);
                    pictureBox.ClientSize = new Size(previewSizeWidth, previewSizeHeight);
                    pictureBox.Image = Paloma.TargaImage.LoadTargaImage(previewFileName);
                }

                //filling treeview image list
                lblMipCount.Text = currentTex.imgList.Count.ToString();
                lblMipCount.Visible = true;
                trvImgList.BeginUpdate();
                trvImgList.Nodes.Clear();
                foreach (ImageInfo imgInfo in currentTex.imgList)
                {
                    string storage;
                    switch (imgInfo.storageType)
                    {
                        case ImageInfo.storage.arcCpr:
                        case ImageInfo.storage.arcUnc:
                            storage = "archive";
                            break;
                        case ImageInfo.storage.pccSto:
                            storage = "pcc";
                            break;
                        default:
                            storage = "null";
                            break;
                    }
                    TreeNode root = trvImgList.Nodes.Add(imgInfo.imgSize.ToString());
                    root.Nodes.Add(storage + " offset: 0x" + imgInfo.offset.ToString("X8"));
                    if (imgInfo.storageType == ImageInfo.storage.empty)
                        continue;
                    root.Nodes.Add("original size: " + imgInfo.uncSize + " bytes");
                    if (imgInfo.storageType == ImageInfo.storage.arcCpr)
                        root.Nodes.Add("compressed size: " + imgInfo.cprSize + " bytes");

                }
                trvImgList.ExpandAll();
                trvImgList.TopNode = trvImgList.Nodes[0];
                trvImgList.EndUpdate();

                List<string> currentPccList = dbHandler.getPccList(texName);

                lblPccCount.Text = currentPccList.Count.ToString();
                lblPccCount.Visible = true;
                trvPccList.BeginUpdate();
                trvPccList.Nodes.Clear();
                foreach (string pccName in currentPccList)
                {
                    trvPccList.Nodes.Add(pccName);
                }
                trvPccList.EndUpdate();
            }
            catch (Exception exc)
            {
                MessageBox.Show("Error: " + exc.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void extractImage(object sender, EventArgs e)
        {
            if (currentTex == null)
                return;

            string imgFilter = "All Files|*.*";

            switch (currentTex.Extension)
            {
                case "dds":
                    imgFilter = "DDS file|*.dds|All Files|*.*";
                    break;
                case "tga":
                    imgFilter = "TGA file|*.tga|All Files|*.*";
                    break;
            }

            saveFileDialog.FileName = currentTex.exportRef.ObjectName + "." + currentTex.Extension;
            saveFileDialog.Filter = imgFilter;

            if (saveFileDialog.ShowDialog() != DialogResult.OK)
                return;

            try
            {
                ImageHandler.extractImage(currentTex, currentTex.imgList.maxUsefulSize).saveToFile(saveFileDialog.FileName);
                MessageBox.Show("Texture " + currentTex.exportRef.ObjectName + " extracted successfully" , "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception exc)
            {
                MessageBox.Show("An error occurred while extracting: " + exc.Message, "Error on extraction", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void replaceImage(object sender, EventArgs e)
        {
            string imgFilter = "All Files|*.*";

            switch (currentTex.Extension)
            {
                case "dds":
                    imgFilter = "DDS file|*.dds|" + imgFilter;
                    break;
                case "tga":
                    imgFilter = "TGA file|*.tga|" + imgFilter;
                    break;
            }

            openFileDialog.Filter = imgFilter;

            if (openFileDialog.ShowDialog() != DialogResult.OK)
                return;

            object funcArgs = new object[2] { currentTex, openFileDialog.FileName };
            new WindowProgressForm(replaceTex, funcArgs).ShowDialog();

            //trvAll_AfterSelect(sender, (TreeViewEventArgs)e);
        }

        private void replaceTex(WindowProgressForm dbprog, object args)
        {
            object[] argList = args as object[];

            Texture2D texture = argList[0] as Texture2D;
            string texReplFile = argList[1] as string;

            if (currentTex == null)
                return;

            int count = 0;

            dbprog.Invoke((Action)delegate
            {
                dbprog.Text = "Replacing textures";
                dbprog.lblCommand.Text = "Replacing " + texture.textureName;
                dbprog.progressBar.Maximum = trvPccList.Nodes.Count;
                dbprog.progressBar.Value = 0;
            });

            try
            {
                string newArchiveName = "amaroktexcache";
                byte[] newGUID = null;
                Texture2D.ImageList imgReplace;

                bool newTfc;

                string tfcUseDefault = iniHnd.IniReadValue("Texplorer", "tfcDefault");
                if (tfcUseDefault == null || tfcUseDefault == "" || Convert.ToBoolean(tfcUseDefault))
                {
                    newTfc = false;
                    imgReplace = ImageHandler.buildImgList(texReplFile, texture);
                }
                else
                {
                    newTfc = true;
                    string tfcFileName = iniHnd.IniReadValue("Texplorer", "tfcFileName");
                    if (tfcFileName != null && tfcFileName != "")
                    {
                        newArchiveName = tfcFileName;
                    }
                    string archiveFileName = ME3Paths.cookedPath + newArchiveName + ".tfc";
                    using (MD5 md5Hash = MD5.Create())
                    {
                        // Convert the input string to a byte array and compute the hash. 
                        newGUID = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(newArchiveName));
                    }

                    if (!File.Exists(archiveFileName))
                    {
                        using (FileStream archiveStream = File.Create(archiveFileName))
                        {
                            archiveStream.WriteBytes(newGUID);
                        }
                    }
                    if (!tocHndl.existsFile(archiveFileName))
                        tocHndl.addFile(archiveFileName, tocHndl.chunkList.Count - 1);

                    imgReplace = ImageHandler.buildImgList(texReplFile, texture, newArchiveName, newGUID);
                }

                string[] pccList = dbHandler.getPccList(texture.textureName).Select(x => ME3Paths.cookedPath + x).Where(x => File.Exists(x)).ToArray();

                //foreach (TreeNode pccNode in trvPccList.Nodes)
                foreach (string pccFile in pccList)
                {
                    if (dbprog.backgroundWorker.CancellationPending)
                    {
                        return;
                    }

                    dbprog.Invoke((Action)delegate { dbprog.progressBar.Value = ++count; dbprog.richTextBox.Text = count + "\\" + trvPccList.Nodes.Count + " - " + texture.textureName; });

                    PCCObject pccObj = new PCCObject(pccFile);
                    Texture2D pccTex2D = new Texture2D(pccObj.Exports[texture.textureName, "Texture2D"]);
                    pccTex2D.imgList = imgReplace;

                    if (pccTex2D.properties.exist("LODGroup"))
                    {
                        (pccTex2D.properties["LODGroup"] as ByteType).Value = "TEXTUREGROUP_Shadowmap";
                        (pccTex2D.properties["LODGroup"] as ByteType).val2 = 0;
                    }
                    else
                    {
                        pccTex2D.properties.Add(new ByteType(pccTex2D.pccRef, "LODGroup", 8, "TextureGroup", "TEXTUREGROUP_Shadowmap", 0));
                    }
                    pccTex2D.properties.Remove("LODBias");
                    pccTex2D.properties.Remove("InternalFormatLODBias");
                    pccTex2D.properties.Remove("MipGenSettings");

                    if (newTfc)
                    {
                        pccTex2D.properties["TFCFileGuid"].Value = newGUID;
                        pccTex2D.properties["TextureFileCacheName"].Value = (string)newArchiveName;
                    }

                    pccTex2D.commitChanges();
                    pccObj.saveToFile(true);
                }
                MessageBox.Show("Texture " + texture.textureName + " has been successfully replaced.", "Replace success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch(Exception exc)
            {
                MessageBox.Show("An error occurred while replacing texture,\n" + exc.Message, "Exception Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void rebuildDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show("This is a very slow procedure, \ndo you want to do it anyway?", "Warning, slow procedure", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.No)
                return;

            dbHandler.showRebuildDB();
        }

        private void readDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            dbHandler.completeTexList(trvAll);
        }

        private void btnSearchAll_Click(object sender, EventArgs e)
        {
            if (tbxAll.Text == "")
                return;

            countTextures = dbHandler.completeTexList(trvAll, null, tbxAll.Text);
        }

        private void btnResetAll_Click(object sender, EventArgs e)
        {
            if (tbxAll.Text == "")
                return;

            tbxAll.Text = "";
            countTextures = dbHandler.completeTexList(trvAll);
        }

        private void tbxAll_KeyUp(object sender, KeyEventArgs e)
        {
            if (tbxAll.Text == "")
                return;

            if (e.KeyCode == Keys.Enter)
                countTextures = dbHandler.completeTexList(trvAll, null, tbxAll.Text);
        }

        private void tbxAll_TextChanged(object sender, EventArgs e)
        {
            if (tbxAll.Text == "")
                countTextures = dbHandler.completeTexList(trvAll);
        }

        #region PccTab

        private void trvPcc_DoubleClick(object sender, EventArgs e)
        {
            if (trvPcc.SelectedNode == null)
                return;

            string pccName = trvPcc.SelectedNode.Text;

            var page = new TabPage(Path.GetFileNameWithoutExtension(pccName));
            var treeview = new TreeView();
            treeview.Name = "treeView";
            treeview.Dock = DockStyle.Fill;
            page.Controls.Add(treeview);
            tabControlPcc.TabPages.Add(page);
            page.Select();

            dbHandler.completeTexList(treeview, pccName);

            treeview.AfterSelect += new TreeViewEventHandler(trvPccTab_AfterSelect);

            tabControlPcc.SelectedIndex = tabControlPcc.TabCount - 1;
        }

        void trvPccTab_AfterSelect(object sender, EventArgs e)
        {
            TreeView trvPccTab = (TreeView)sender;
            TreeNode nodeSel = trvPccTab.SelectedNode;
            openTextureData(nodeSel);
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (tabControlPcc.SelectedTab.Text == defaultPccTabName)
                return;

            tabControlPcc.TabPages.Remove(tabControlPcc.SelectedTab);
        }

        private void closeAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (TabPage tabPage in tabControlPcc.TabPages)
            {
                if (tabPage.Text != defaultPccTabName)
                    tabControlPcc.TabPages.Remove(tabPage);
            }
        }

        private void searchPccOrTex()
        {
            if (tbxPcc.Text == "")
                return;

            if (tabControlPcc.SelectedTab.Text == defaultPccTabName)
            {
                dbHandler.completePccList(trvPcc, tbxPcc.Text);
                /*if (trvPcc.Nodes.Count <= 1)
                {
                    MessageBox.Show("No matches found.");
                    dbHandler.completePccList(trvPcc);
                }*/
            }
            else
            {
                TabPage currentTab = tabControlPcc.SelectedTab;
                TreeView currentTrv = (TreeView)currentTab.Controls.Find("treeView", true)[0];
                string selectedPcc = currentTab.Text + ".pcc";

                dbHandler.completeTexList(currentTrv, selectedPcc, tbxPcc.Text);

                if (currentTrv.Nodes.Count == 0)
                {
                    MessageBox.Show("No matches found.");
                    dbHandler.completeTexList(currentTrv, selectedPcc);
                }
            }
        }

        private void btnSearchPcc_Click(object sender, EventArgs e)
        {
            searchPccOrTex();
        }

        private void btnResetPcc_Click(object sender, EventArgs e)
        {
            tbxPcc.Text = "";

            if (tabControlPcc.SelectedTab.Text == defaultPccTabName)
            {
                dbHandler.completePccList(trvPcc);
            }
            else
            {
                TabPage currentTab = tabControlPcc.SelectedTab;
                TreeView currentTrv = (TreeView)currentTab.Controls.Find("treeView", true)[0];
                string selectedPcc = currentTab.Text + ".pcc";

                dbHandler.completeTexList(currentTrv, selectedPcc);
            }
        }

        private void tbxPcc_KeyUp(object sender, KeyEventArgs e)
        {
            if (tbxPcc.Text == "")
                return;

            if (e.KeyCode == Keys.Enter)
            {
                searchPccOrTex();
            }
        }

        #endregion

        private void importToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog.Filter = "Mod file|*.atm|All files|*.*";
            if (openFileDialog.ShowDialog() != DialogResult.OK)
                return;

            new WindowProgressForm(importMod, openFileDialog.FileName).ShowDialog();
        }

        private void importMod(WindowProgressForm winProg, object args)
        {
            try
            {
                string zipFileName = args as string;

                string tempFolder = Path.GetTempPath() + @"AmaroK86\Texplorer\temp";
                //MessageBox.Show("file name: " + zipFileName);

                if (Directory.Exists(tempFolder))
                    Directory.Delete(tempFolder, true);

                if (!Directory.Exists(tempFolder))
                    Directory.CreateDirectory(tempFolder);

                UnZipper uz = new UnZipper();
                uz.Destination = tempFolder;
                uz.IfFileExist = enIfFileExist.Overwrite;
                uz.ItemList.Add("*.*");
                uz.Recurse = true;
                uz.ZipFile = zipFileName;
                uz.UnZip();

                //System.Diagnostics.Process.Start(tempFolder);

                List<string> filePaths = Directory.GetFiles(tempFolder).Where(file => Regex.IsMatch(file, @"^.+\.([d|D][d|D][s|S]|[t|T][g|G][a|A])$")).ToList();
                foreach (string fileName in filePaths)
                {
                    string texName = Path.GetFileNameWithoutExtension(fileName);
                    string pccFile = ME3Paths.cookedPath + dbHandler.getPccList(texName).First();
                    if (!File.Exists(pccFile))
                        throw new Exception("invalid file name " + texName);

                    PCCObject pccObj = new PCCObject(pccFile);
                    Texture2D tex2D = new Texture2D(pccObj.Exports[texName, "Texture2D"]);


                }
                //Directory.GetFiles(tempFolder, "*.dds").ToList();
            }
            catch (Exception exc)
            {
                MessageBox.Show("Error: " + exc.Message,"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            About aboutWindow = new About();
            aboutWindow.ShowDialog();
        }

        private void preferencesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            new frmPreferences().ShowDialog();
        }
    }
}
